Simulating transactions
Overview
It's time to simulate the real-world behavior of your card program using the Core API.
For a deep dive on how transactions, payments, and authorizations work see Transaction Lifecyle and Transaction States and Types.
Goals
At the end of this guide, you should have been able to:
- Check the balance of a card
- Simulate a transaction capturing funds in a single request
- Simulate a transaction capturing funds in multiple requests
- Simulate a refund.
Simulating transactions is a feature only available in the sandbox environment and intended for testing your integration.
Prerequisites
Before simulating transactions, you'll need:
- An API Key for making calls to the Core API.
- Test cardholders, you can create them following this guide.
- Each test cardholder must be issued a card.
- Each card will need to have funds to draw from.
The nature of a transaction
A transaction operation is composed of four steps:
- Authorization
- Holding funds
- Clearing funds
- Settlement
Step 1 (authorization) and Step 2 (holding funds) happen synchronously one after the other. However, Step 3 (clearing funds) and Step 4 (settlement) happen at a later time–as late as at the end of the day, which allows the merchant to amend the transaction amount or even cancel it.
When simulating transactions, we're going to simulate Step 1 and Step 2 and Step 3 through the Core API.
Step 4 is performed by Apto, so is not important for us in this case.
Indicating transaction type
When simulating transactions, we're going to indicate to the Core API what type of transaction we're simulating via the type
field in the POST
request, and how to process it via the processing_type
field.
The processing_type
field indicates whether the transaction should attempt to authorize and capture funds in a single or multiple API calls and whether it can be declined or not.
Remember the "capture" request sent by the merchant–in this case, as you're simulating the transaction, will trigger clearing funds and settlement, which marks the transaction as complete and moves funds to the merchant account.
Checking the card balance
First, verify whether the card we want to simulate transactions on has enough funds.
Send a query to the /cards/{CARD_ID}
endpoint to retrieve the card's details:
curl --location --request GET 'https://api.sbx.aptopayments.com/cards/{CARD_ID}' \
--header 'Authorization: Basic {CORE_API_KEY}'
The response will contain fields indicating the card total balance, and how much is available for use.
{
"card": {
"id": "crd_235ec5ec0032c815",
"activated_at": "2020-11-19T19:09:31Z",
"created_at": "2020-11-19T19:09:31Z",
// ...
"spendable_today": {
"amount": 105.33,
"currency": "USD",
"native_amount": 105.33,
"native_currency": "USD"
},
"spendable_balance": {
"amount": 105.33,
"currency": "USD",
"native_amount": 105.33,
"native_currency": "USD"
},
"total_balance": {
"amount": 105.33,
"currency": "USD",
"native_amount": 105.33,
"native_currency": "USD"
}
}
}
In this case the card has a total balance of $105.33
so purchases can be made with it.
Authorization and capturing funds in a single request
The simplest transactions to simulate are those where authorization and capture are attempted with a single request.
These types of transactions have processing_type
as financial_request
. The following two examples show a declined transaction and an approved transaction.
Declined financial request
If a cardholder tries to spend more money than they have, the transaction request will be declined due to insufficient funds.
Submit a POST request to the /cards/{card_id}/transactions
endpoint with a billing amount greater than the card balance.
- cURL
- JSON
curl --location --request POST 'https://api.sbx.aptopayments.com/cards/{CARD_ID}/transactions' \
--header 'Authorization: Basic {CORE_API_KEY}' \
--header 'Content-Type: application/json' \
--data-raw '{
"billing_amount": 999.34,
"processing_type": "financial_request",
"type": "purchase",
"description": "Test"
}'
{
"billing_amount": 999.34,
"processing_type": "financial_request",
"type": "purchase",
"description": "Test"
}
The response should show that the transaction was declined due to insufficient funds on the card, as indicated in the decline_reason
field:
{
"transaction": {
"id": "txn_724ce490de1a54e1",
"authorizations": [
{
"authorization": {
"id": "auth_4801faf4602d0705",
"issuer_data": null,
"decline_code": "decline_nsf",
"decline_reason": "INSUFFICIENT FUNDS",
"external_authorization_id": null,
"authorized": false,
"created_at": "2020-11-19T19:57:47Z"
}
}
],
"adjustments": []
// ...
}
}
You can learn more about the different decline codes in the Core API documentation.
Approved financial request
Alternatively, if a cardholder makes a transaction with an amount less than the card's total balance, the transaction should be approved:
- cURL
- JSON
curl --location --request POST 'https://api.sbx.aptopayments.com/cards/{CARD_ID}/transactions' \
--header 'Authorization: Basic {CORE_API_KEY}' \
--header 'Content-Type: application/json' \
--data-raw '{
"billing_amount": 8.12,
"processing_type": "financial_request",
"type": "purchase",
"description": "Test"
}'
{
"billing_amount": 8.12,
"processing_type": "financial_request",
"type": "purchase",
"description": "Test"
}
The response will show that the transaction was approved, as reflected in the "authorized": true
field and the "state": complete
field.
{
"transaction": {
"id": "txn_eb5273a61f9e1354",
"authorizations": [
{
"authorization": {
"id": "auth_3f825a465342cbe3",
"issuer_data": null,
"decline_code": null,
"decline_reason": null,
"external_authorization_id": null,
"authorized": true,
"created_at": "2020-11-19T20:00:05Z"
}
}
],
"adjustments": [
{
"adjustment": {
"id": "adj_d557583a99004fbc",
"created_at": "2020-11-19T20:00:05Z",
"local_amount": {
"amount": -8.12,
"currency": "USD"
},
"billing_amount": {
"amount": -8.12,
"currency": "USD"
},
"native_amount": {
"amount": -8.12,
"currency": "USD"
},
"native_fee_amount": {
"amount": -0.0,
"currency": "USD"
},
"funding_source_transaction_id": null,
"funding_source_name": "",
"exchange_rate": 1.0,
"type": "capture"
}
}
],
// ...
"state": "complete"
// ...
}
}
In this transaction, both the authorization and capture steps occurred within a single request.
Visit the developer portal and click on the "Transactions" tab, you should see the two transactions we simulated:
Authorization and capture as separate requests
In a multi-message authorization and capture process, the merchant requests an authorization for a purchase but keeps the option to change the amount. The final amount is sometimes different than the original authorization request.
For example, you might see a transaction like this when you deny a retailer’s request to “round up” your purchase amount to donate to a charity.
To simulate this scenario, you’ll make multiple API calls for each transaction, each time:
- The first API call
- represents a merchant requesting verification of the card and its available balance.
- Subsequent API call(s)
- represents the merchant confirmation for capturing the funds.
Declined authorization request
Similar to financial request transactions, you can simulate a declined authorization request transaction by simulating the cardholder attempting to spend more money than their total available balance.
Because our request will be declined, we will not send a second API call for capturing the funds.
- cURL
- JSON
curl --location --request POST 'https://api.sbx.aptopayments.com/cards/{CARD_ID}/transactions' \
--header 'Authorization: Basic {CORE_API_KEY}' \
--header 'Content-Type: application/json' \
--data-raw '{
"billing_amount": 999.34,
"processing_type": "authorization_request",
"type": "purchase",
"description": "Test"
}'
{
"billing_amount": 999.34,
"processing_type": "financial_request",
"type": "purchase",
"description": "Test"
}
The response should show that the transaction was declined due to insufficient funds on the card, as indicated by the decline_reason
field:
{
"transaction": {
"id": "txn_724ce490de1a54e1",
"authorizations": [
{
"authorization": {
"id": "auth_4801faf4602d0705",
"issuer_data": null,
"decline_code": "decline_nsf",
"decline_reason": "INSUFFICIENT FUNDS",
"external_authorization_id": null,
"authorized": false,
"created_at": "2020-11-19T19:57:47Z"
}
}
],
"adjustments": []
// ...
}
}
You can learn more about the different decline codes in the Core API documentation.
Approved authorization request – equal to the pending amount
Submit an initial POST request to the /cards/{card_id}/transactions
endpoint with a billing amount less than the card balance:
- cURL
- JSON
curl --location --request POST 'https://api.sbx.aptopayments.com/cards/{CARD_ID}/transactions' \
--header 'Authorization: Basic {CORE_API_KEY}' \
--header 'Content-Type: application/json' \
--data-raw '{
"billing_amount": 10.15,
"processing_type": "authorization_request",
"type": "purchase",
"description": "Test"
}'
{
"billing_amount": 10.15,
"processing_type": "authorization_request",
"type": "purchase",
"description": "Test"
}
The response will indicate that the transaction state is pending ("state": "pending"
) given you have not asked to capture the funds on the cardholder account yet.
{
"transaction": {
"livemode": true,
"id": "txn_d9ab131480f8ff34",
"authorizations": [
{
"authorization": {
"id": "auth_5edc22f5e5241314",
"issuer_data": null,
"decline_code": null,
"decline_reason": null,
"external_authorization_id": null,
"authorized": true,
"created_at": "2020-11-19T21:50:16Z"
}
}
],
"adjustments": [
{
"adjustment": {
"id": "adj_a843a8e498b28766",
"created_at": "2020-11-19T21:50:16Z",
"local_amount": {
"amount": -10.15,
"currency": "USD"
},
"billing_amount": {
"amount": -10.15,
"currency": "USD"
},
"native_amount": {
"amount": -10.15,
"currency": "USD"
},
"native_fee_amount": {
"amount": -0.0,
"currency": "USD"
},
"funding_source_transaction_id": null,
"funding_source_name": "",
"exchange_rate": 1.0,
"type": "capture"
}
}
],
// ...
"state": "pending"
// ...
}
}
The recently authorized transaction can now be captured. In this case, it is important that the request to capture the funds should bill an equal amount to the original authorized amount.
Send a PUT request to the /cards/{card_id}/transactions/{transaction_id}
endpoint, supplying the transaction ID from the previous response and the matching authorized amount:
- cURL
- JSON
curl --location --request POST 'https://api.sbx.aptopayments.com/cards/{CARD_ID}/transactions/{TR_ID}' \
--header 'Authorization: Basic {CORE_API_KEY}' \
--header 'Content-Type: application/json' \
--data-raw '{
"billing_amount": 10.15,
"processing_type": "financial_advice"
}'
{
"billing_amount": 10.15,
"processing_type": "financial_advice"
}
If all went well, the response will include a "state": "complete"
field, indicating the transaction is now complete:
{
"transaction": {
"id": "txn_d9ab131480f8ff34",
"authorizations": [
{
"authorization": {
"id": "auth_5edc22f5e5241314",
"issuer_data": null,
"decline_code": null,
"decline_reason": null,
"external_authorization_id": null,
"authorized": true,
"created_at": "2020-11-19T21:50:16Z"
}
}
],
"adjustments": [
{
"adjustment": {
"id": "adj_a843a8e498b28766",
"created_at": "2020-11-19T21:50:16Z",
"local_amount": {
"amount": -10.15,
"currency": "USD"
},
"billing_amount": {
"amount": -10.15,
"currency": "USD"
},
"native_amount": {
"amount": -10.15,
"currency": "USD"
},
"native_fee_amount": {
"amount": -0.0,
"currency": "USD"
},
"funding_source_transaction_id": null,
"funding_source_name": "",
"exchange_rate": 1.0,
"type": "capture"
}
}
],
// ...
"state": "complete",
// ...
}
Approved authorization request – less than the pending amount
In a multi-message authorization and capture process, the merchant requests an authorization for a purchase but keeps the option to change the amount.
The final amount is sometimes less than the original authorization request. For example, you might see a transaction like this when you buy fuel from a gas station.
Submit a POST request to the /cards/{card_id}/transactions
endpoint with a billing amount less than the card balance. When the request is approved, note the transaction ID in the response. You’ll need it to update the transaction with the final purchase amount. The approved request will have a state of pending.
- cURL
- JSON
curl --location --request POST 'https://api.sbx.aptopayments.com/cards/{CARD_ID}/transactions' \
--header 'Authorization: Basic {CORE_API_KEY}' \
--header 'Content-Type: application/json' \
--data-raw '{
"billing_amount": 12.34,
"processing_type": "authorization_request",
"type": "purchase",
"description": "Test"
}'
{
"billing_amount": 12.34,
"processing_type": "authorization_request",
"type": "purchase",
"description": "Test"
}
{
"transaction": {
"id": "txn_93879ffb2b5c14a8",
"authorizations": [
{
"authorization": {
"id": "auth_3ab56a72a41a30e1",
"issuer_data": null,
"decline_code": null,
"decline_reason": null,
"external_authorization_id": null,
"authorized": true,
"created_at": "2020-11-19T19:44:23Z"
}
}
],
"adjustments": [
{
"adjustment": {
"id": "adj_66556b71ce1d908c",
"created_at": "2020-11-19T19:44:23Z",
"local_amount": {
"amount": -12.34,
"currency": "USD"
},
"billing_amount": {
"amount": -12.34,
"currency": "USD"
},
"native_amount": {
"amount": -12.34,
"currency": "USD"
},
"native_fee_amount": {
"amount": -0.0,
"currency": "USD"
},
"funding_source_transaction_id": null,
"funding_source_name": "",
"exchange_rate": 1.0,
"type": "capture"
}
}
],
// ...
"state": "pending",
// ...
}
Notice the state is pending
. You can now complete the previously authorized transaction, informing the issuer of the final amount. In this case, the cleared transaction is less than the original authorized amount.
When making the capture request, you must specify a smaller billing amount ($12
) than what was stated in the initial authorization request ($12.34
):
- cURL
- JSON
curl --location --request PUT 'https://api.sbx.aptopayments.com/cards/{CARD_ID}/transactions/{TR_ID}' \
--header 'Authorization: Basic {CORE_API_KEY}' \
--header 'Content-Type: application/json' \
--data-raw '{
"billing_amount": 12.00,
"processing_type": "financial_advice"
}'
{
"billing_amount": 12.0,
"processing_type": "financial_advice"
}
{
"transaction": {
"id": "txn_93879ffb2b5c14a8",
"authorizations": [
{
"authorization": {
"id": "auth_3ab56a72a41a30e1",
"issuer_data": null,
"decline_code": null,
"decline_reason": null,
"external_authorization_id": null,
"authorized": true,
"created_at": "2020-11-19T19:44:23Z"
}
}
],
"adjustments": [
{
"adjustment": {
"id": "adj_5e1cfe1020273b50",
"created_at": "2020-11-19T19:53:47Z",
"local_amount": {
"amount": 0.34,
"currency": "USD"
},
"billing_amount": {
"amount": 0.34,
"currency": "USD"
},
"native_amount": {
"amount": 0.34,
"currency": "USD"
},
"native_fee_amount": {
"amount": 0.0,
"currency": "USD"
},
"funding_source_transaction_id": null,
"funding_source_name": "",
"exchange_rate": 1.0,
"type": "refund"
}
},
{
"adjustment": {
"id": "adj_66556b71ce1d908c",
"created_at": "2020-11-19T19:44:23Z",
"local_amount": {
"amount": -12.34,
"currency": "USD"
},
"billing_amount": {
"amount": -12.34,
"currency": "USD"
},
"native_amount": {
"amount": -12.34,
"currency": "USD"
},
"native_fee_amount": {
"amount": -0.0,
"currency": "USD"
},
"funding_source_transaction_id": null,
"funding_source_name": "",
"exchange_rate": 1.0,
"type": "capture"
}
}
],
// ...
"state": "complete",
// ...
}
Notice the response includes "state": "complete"
. This completes the transaction.
Approved authorization request – greater than the pending amount
The final amount for a transaction can be sometimes greater than the original authorization request amount. For example, you might see a transaction like this when you buy a meal at a restaurant and leave a tip.
To simulate this scenario, let's create the same authorization request with a billing amount of $21.22
:
- cURL
- JSON
curl --location --request POST 'https://api.sbx.aptopayments.com/cards/{CARD_ID}/transactions' \
--header 'Authorization: Basic {CORE_API_KEY}' \
--header 'Content-Type: application/json' \
--data-raw '{
"billing_amount": 21.22,
"processing_type": "authorization_request",
"type": "purchase",
"description": "Test"
}'
{
"billing_amount": 21.22,
"processing_type": "authorization_request",
"type": "purchase",
"description": "Test"
}
When making the capture request, this time you will specify a greater billing amount ($25
) than what was stated in the initial authorization request ($21.22
):
- cURL
- JSON
curl --location --request PUT 'https://api.sbx.aptopayments.com/cards/{CARD_ID}/transactions/{TR_ID}' \
--header 'Authorization: Basic {CORE_API_KEY}' \
--header 'Content-Type: application/json' \
--data-raw '{
"billing_amount": 25.0,
"processing_type": "financial_advice"
}'
{
"billing_amount": 25.0,
"processing_type": "financial_advice"
}
If all went well, the response will include a "state": "complete"
field, indicating the transaction is now complete:
{
"transaction": {
"id": "txn_d9ab131480f8ff34",
"authorizations": [
{
"authorization": {
"id": "auth_5edc22f5e5241314",
"issuer_data": null,
"decline_code": null,
"decline_reason": null,
"external_authorization_id": null,
"authorized": true,
"created_at": "2020-11-19T21:50:16Z"
}
}
],
// ...
"state": "complete",
// ...
}
Visit the developer portal and click on the "Transactions" tab, you should all the transactions we simulated so far:
Refund
Refunds can come in various forms depending on how the merchant processes the refund.
This example simulates a very common merchant refund where the refund is a financial advice, also known as a force post, which Apto cannot decline.
Submit a POST request to the /cards/{card_id}/transactions
endpoint with a billing amount equivalent to the desired amount to be refunded and specifying the transaction type as refund
:
- cURL
- JSON
curl --location --request POST 'https://api.sbx.aptopayments.com/cards/{CARD_ID}/transactions' \
--header 'Authorization: Basic {CORE_API_KEY}' \
--header 'Content-Type: application/json' \
--data-raw '{
"billing_amount": 105.33,
"processing_type": "financial_advice",
"type": "return",
"description": "Test"
}'
{
"billing_amount": 105.33,
"processing_type": "financial_advice",
"type": "return",
"description": "Test"
}
If all went well, the response will contain a "state": "complete"
field indicating you have successfully refunded $105.33
back to the card.
{
"transaction": {
"id": "txn_f8332b818575e541",
"authorizations": [],
// ...
"state": "complete",
"type": "REFUND",
"billing_amount": {
"currency": "USD",
"amount": 105.33
}
}
}
Glossary
Transaction processing types
authorization_request
Creates a temporary hold on a card. The amount adjusts the cardholder's balance, but does not affect settlement. It is just securing the funds on the balance in case the merchant later sends a settlement message.
An Authorization Request can be declined by the cardholder's balance. In production, this is a synchronous API call that is time sensitive. Authorization requests are usually automatically released within three working days, and they should never exist beyond one month.
financial_request
Similar to Authorization Request actions, these do not need to adjust the amount or need to create a temporary hold. This means that the card networks will claim these funds during the following day’s settlement.
financial_advice
Otherwise known as force posts, these create a transaction without a previous hold having been made on the account. These are rare scenarios, and are not time-sensitive transactions. If they induce a negative balance situation, they are usually resolved with the cardholder or by disputing the transaction with the networks. Apto cannot decline these messages.